﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.Xna.Framework;
using JigLibX.Collision;
using JigLibX.Physics;
using JigLibX.Geometry;
using JigLibX.Math;

namespace DARE
{
    public class CCharacterControllerPath : CCharacterController
    {

    #region fields

        private Vector3 m_moveToPos;

        private IPathSearch m_aStar;
        private Thread m_searchThread;
        private CNodePath m_nodePath;
        private LinkedList<IPathNode>.Enumerator m_enumNode;

        private volatile bool m_searchComplete;
        private volatile bool m_searchFailed;
        private bool m_pathSmoothed;
        private bool m_moveOnPath;

        private CWay m_curWay;
        private CWay m_oldWay;

        private Vector3 m_oldPos;

        private int m_nbSearch;

    #endregion

    #region properties

        public IPathSearch SearchSystem
        {
            get { return m_aStar; }
        }

    #endregion

    #region ctor

        public CCharacterControllerPath(CNode node) :
            base(node)
        { }

        public CCharacterControllerPath(CEntity entity) :
            base(entity.Node)
        { }

        public override void Initialize()
        {
            base.Initialize();

            // initialize pathfinding
            m_aStar = new CAStar();
            m_aStar.AllowDiagonalMove = true;

            m_curWay = new CWay();
            m_oldWay = new CWay();

            m_nbSearch = 0;

            m_searchComplete = true;
            m_searchFailed = true;
            m_pathSmoothed = false;
            m_moveOnPath = false;

            // set the event
            m_aStar.eSearchComplete += new EventHandler(AStarSearchComplete);
            m_aStar.eSearchFailed += new EventHandler(AStarSearchFailed);
        }

    #endregion

    #region run

        public override void UpdateController(float dt)
        {
            // case changed ! test if we can reach the target without hitting objects
            if (((int)m_node.Position.X != (int)m_oldPos.X) ||
                 (int)m_node.Position.Z != (int)m_oldPos.Z)
            {
                CDare.Instance.DebugMgr.DebugConsole.WriteLine("actual case changed to " + 
                    ((int)(m_node.Position.X)).ToString() + " " + ((int)(m_node.Position.Z)).ToString());

                if (!IntersectWall((int)m_node.Position.X, -(int)m_node.Position.Z,
                    (int)m_moveToPos.X, -(int)m_moveToPos.Z))
                        base.MoveTo(m_moveToPos);
                // avoid to call the search thread
                //else
                    
            }
            ApplyMovement();
            //else
            //{
                ProcessPathFinding();
            //}
            base.UpdateController(dt);
            m_oldPos = m_node.Position;
        }

        private void ProcessPathFinding()
        {
            // if the search is complete and the new path has not yet been smoothed, we do it
            if (m_searchComplete && !m_pathSmoothed)
            {
                if (m_aStar.Path != null)
                {
                    m_moveOnPath = true;
                    m_enumNode = m_aStar.Path.GetEnumerator();
                }
                m_pathSmoothed = true;
            }

            // if the search is complete and data changed, we launch it again
            if ((m_searchComplete || m_searchFailed) &&
                (m_curWay.sX != m_oldWay.sX || m_curWay.sY != m_oldWay.sY ||
                 m_curWay.eX != m_oldWay.eX || m_curWay.eY != m_oldWay.eY))
            {
                m_searchComplete = false;
                m_searchFailed = false;
                m_pathSmoothed = false;

                CDare.Instance.DebugMgr.DebugConsole.WriteLine("lauching search #" + (++m_nbSearch).ToString());

                m_searchThread = new Thread(new ParameterizedThreadStart(m_aStar.Search));
                m_searchThread.Start(m_curWay);
            }
            m_oldWay = m_curWay;
        }

        private void ApplyMovement()
        {
            if (m_moveOnPath && !IsMoving)
            {
                m_enumNode.MoveNext();
                if (m_enumNode.Current != null)
                    base.MoveTo(new Vector3(m_enumNode.Current.X + 0.5f, 0, -m_enumNode.Current.Y - 0.5f));
                else
                {
                    base.MoveTo(m_moveToPos);
                    m_moveOnPath = false;
                }
            }
        }

    #endregion

    #region methods

        protected virtual void AStarSearchComplete(object sender, EventArgs e)
        {
            m_searchComplete = true;
            CDare.Instance.DebugMgr.DebugConsole.WriteLine("search #" + m_nbSearch.ToString() + " complete !\n"); 
        }

        protected virtual void AStarSearchFailed(object sender, EventArgs e)
        {
            m_searchFailed = true;
            CDare.Instance.DebugMgr.DebugConsole.WriteLine("search #" + m_nbSearch.ToString() + " failed !\n"); 
        }

        public virtual void MoveTo(Vector3 position)
        {
            m_moveToPos = position;

            // launch the search only if we are in a new case
            if (((int)position.X != (int)m_node.Position.X) || ((int)position.Z != (int)m_node.Position.Z))
                m_curWay = new CWay((int)m_node.Position.X, -(int)m_node.Position.Z, (int)position.X, -(int)position.Z);
            // set the old pos different from the actual node pos because we need to detect that the case changed in the first case
            //m_oldPos = m_node.Position + Vector3.One;
            if (!IntersectWall((int)m_node.Position.X, -(int)m_node.Position.Z,
               (int)m_moveToPos.X, -(int)m_moveToPos.Z))
            {
                // avoid pathfinding
                m_oldWay = m_curWay;
                base.MoveTo(position);
            }

            //// we are in the same so we just move to the new pos
            //else
            //    base.MoveTo(position);
        }

        public bool IntersectWall(int x0, int y0, int x1, int y1)
        {
            int dx = Math.Abs(x1 - x0);
            int dy = Math.Abs(y1 - y0);

            int sx = x0 < x1 ? 1 : -1;
            int sy = y0 < y1 ? 1 : -1;

            int err = dx - dy;
            int e2;

            while (true)
            {
                // pixel here
                if (!m_aStar.SearchSpace[x0, y0].IsWalkable)
                    return true;
                if (x0 == x1 && y0 == y1)
                    break;
                e2 = 2 * err;
                if (e2 > -dy)
                {
                    err -= dy;
                    x0 += sx;
                }
                if (e2 < dx)
                {
                    err += dx;
                    y0 += sy;
                }
            }
            return false;
        }

    #endregion

    }
}
